Skip to content

Conversation

@vadikko2
Copy link
Owner

Decouple Interfaces from Pydantic Types

📋 Summary

This PR introduces a major refactoring that decouples core interfaces (IEvent, IRequest, IResponse) from Pydantic-specific implementations. The library now supports both Pydantic-based and Dataclass-based implementations, providing flexibility for users who want to avoid Pydantic dependency or prefer lightweight dataclass implementations.

🎯 Motivation

Previously, the library was tightly coupled to Pydantic types, making it difficult for users who wanted to use alternative implementations. This refactoring:

  • Reduces coupling: Core interfaces are now independent of Pydantic
  • Increases flexibility: Users can choose between Pydantic and Dataclass implementations
  • Maintains backward compatibility: Default implementations still use Pydantic
  • Enables mix-and-match: Users can combine different types (e.g., Pydantic request with Dataclass response)

🔄 Changes Overview

Core Interfaces

  • IEvent: Abstract interface for event-type objects
  • IRequest: Abstract interface for request-type objects
  • IResponse: Abstract interface for response-type objects

New Implementations

Dataclass-based (Lightweight)

  • DCEvent: Dataclass implementation of IEvent
  • DCDomainEvent: Dataclass implementation of domain events
  • DCNotificationEvent: Dataclass implementation of notification events
  • DCRequest: Dataclass implementation of IRequest
  • DCResponse: Dataclass implementation of IResponse

Pydantic-based (Default, with validation)

  • PydanticEvent: Pydantic implementation of IEvent (aliased as Event)
  • PydanticDomainEvent: Pydantic implementation of domain events (aliased as DomainEvent)
  • PydanticNotificationEvent: Pydantic implementation of notification events (aliased as NotificationEvent)
  • PydanticRequest: Pydantic implementation of IRequest (aliased as Request)
  • PydanticResponse: Pydantic implementation of IResponse (aliased as Response)

📁 Files Changed

Core Library Files (49 files, +1435/-172 lines)

Event System

  • src/cqrs/events/event.py - Introduced IEvent interface and implementations
  • src/cqrs/events/__init__.py - Updated exports
  • src/cqrs/events/event_emitter.py - Updated to work with IEvent
  • src/cqrs/events/event_handler.py - Updated to work with IEvent
  • src/cqrs/events/event_processor.py - Updated to work with IEvent
  • src/cqrs/events/map.py - Updated to work with IEvent

Request/Response System

  • src/cqrs/requests/request.py - Introduced IRequest interface and implementations
  • src/cqrs/response.py - Introduced IResponse interface and implementations
  • src/cqrs/requests/request_handler.py - Updated to work with interfaces
  • src/cqrs/requests/map.py - Updated to work with interfaces
  • src/cqrs/requests/mermaid.py - Updated to work with interfaces
  • src/cqrs/requests/cor_request_handler.py - Updated to work with interfaces

Dispatchers

  • src/cqrs/dispatcher/event.py - Updated to work with IEvent
  • src/cqrs/dispatcher/request.py - Updated to work with interfaces
  • src/cqrs/dispatcher/streaming.py - Updated to work with interfaces
  • src/cqrs/dispatcher/models.py - Updated type hints

Serializers/Deserializers

  • src/cqrs/deserializers/json.py - Updated to work with interfaces
  • src/cqrs/deserializers/protobuf.py - Updated to work with interfaces
  • src/cqrs/deserializers/exceptions.py - Updated exception handling
  • src/cqrs/serializers/default.py - Updated to work with interfaces
  • src/cqrs/serializers/protobuf.py - Updated to work with interfaces

Other Components

  • src/cqrs/mediator.py - Updated to work with interfaces
  • src/cqrs/producer.py - Updated to work with IEvent
  • src/cqrs/outbox/* - Updated outbox components to work with IEvent
  • src/cqrs/message_brokers/protocol.py - Updated protocol definitions
  • src/cqrs/middlewares/logging.py - Updated middleware
  • src/cqrs/container/dependency_injector.py - Updated DI integration
  • src/cqrs/saga/step.py - Updated saga steps
  • src/cqrs/saga/recovery.py - Updated saga recovery
  • src/cqrs/saga/mermaid.py - Updated saga visualization
  • src/cqrs/types.py - Updated type definitions
  • src/cqrs/__init__.py - Updated public API exports

Examples

  • examples/request_response_types.py - NEW: Comprehensive example demonstrating different request/response type combinations
  • examples/kafka_event_consuming.py - Updated to use interfaces
  • examples/saga_fastapi_sse.py - Updated to use interfaces

Tests

  • tests/unit/test_deserializers.py - Updated tests for new interfaces
  • tests/unit/test_event_processor.py - Updated tests
  • tests/unit/test_cor_request_handler.py - Updated tests
  • tests/unit/test_request_mediator_parallel_events.py - Updated tests
  • tests/unit/test_streaming_dispatcher.py - Updated tests
  • tests/unit/test_streaming_mediator.py - Updated tests
  • tests/integration/test_event_outbox.py - Updated integration tests
  • tests/integration/test_streaming_mediator.py - Updated integration tests

Documentation

  • README.md - Updated documentation with new interfaces and examples
  • pyproject.toml - Updated project metadata if needed

✨ Key Features

1. Interface-Based Design

All core types now implement abstract interfaces, allowing for multiple implementations:

# Interface
class IRequest(abc.ABC):
    def to_dict(self) -> dict: ...
    @classmethod
    def from_dict(cls, **kwargs) -> typing.Self: ...

2. Multiple Implementation Options

Pydantic (Default):

class CreateUserCommand(PydanticRequest):
    username: str
    email: str

Dataclass (Lightweight):

@dataclasses.dataclass
class CreateProductCommand(DCRequest):
    name: str
    price: float

3. Mix and Match Support

Users can combine different types based on their needs:

# Pydantic request with Dataclass response
class CreateOrderCommand(PydanticRequest): ...
@dataclasses.dataclass
class OrderResponse(DCResponse): ...

4. Backward Compatibility

  • Default type aliases (Request, Response, Event) still point to Pydantic implementations
  • Existing code using Pydantic types continues to work without changes
  • All Pydantic features (validation, serialization) remain available

🧪 Testing

All existing tests have been updated to work with the new interface-based design. The test suite covers:

  • ✅ Interface implementations (Pydantic and Dataclass)
  • ✅ Serialization/deserialization for both types
  • ✅ Event processing with different event types
  • ✅ Request/response handling with different types
  • ✅ Integration tests with message brokers
  • ✅ Saga workflows with different types

📚 Documentation

  • Updated README with interface documentation
  • Added comprehensive example (examples/request_response_types.py) demonstrating:
    • Pydantic-based requests and responses
    • Dataclass-based requests and responses
    • Mixed usage patterns
    • Serialization/deserialization
    • Validation examples

🔧 Migration Guide

For Existing Users

No changes required! The library maintains backward compatibility:

# This still works exactly as before
class MyCommand(Request):  # Request is still PydanticRequest
    field: str

For New Users Wanting Dataclass Support

# Use DCRequest/DCResponse for lightweight implementations
@dataclasses.dataclass
class MyCommand(DCRequest):
    field: str

🎁 Benefits

  1. Flexibility: Choose the implementation that fits your needs
  2. Reduced Dependencies: Use dataclasses to avoid Pydantic if desired
  3. Type Safety: Interfaces ensure consistent behavior across implementations
  4. Extensibility: Easy to add new implementations in the future
  5. Backward Compatible: Existing code continues to work

📊 Statistics

  • Files Changed: 49
  • Lines Added: +1435
  • Lines Removed: -172
  • Net Change: +1263 lines
  • New Files: 1 (examples/request_response_types.py)
  • Commits: 4 (including linter fixes)

✅ Checklist

  • Core interfaces defined (IEvent, IRequest, IResponse)
  • Dataclass implementations added
  • Pydantic implementations refactored to implement interfaces
  • All library components updated to use interfaces
  • Tests updated and passing
  • Examples updated
  • Documentation updated
  • Backward compatibility maintained
  • Linter checks passed

🔗 Related Issues

This PR addresses the need for decoupling from Pydantic while maintaining full backward compatibility and adding flexibility for users who prefer alternative implementations.


Note: This is a significant refactoring that improves the library's architecture while maintaining backward compatibility. All existing functionality remains intact, and new options are available for users who want them.

@vadikko2 vadikko2 merged commit fe57add into master Jan 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants